home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tech Arsenal 1
/
Tech Arsenal (Arsenal Computer).ISO
/
tek-04
/
gs24src.zip
/
TRACE.C
< prev
next >
Wrap
C/C++ Source or Header
|
1992-02-16
|
15KB
|
516 lines
/* Copyright (C) 1989, 1990 Aladdin Enterprises. All rights reserved.
Distributed by Free Software Foundation, Inc.
This file is part of Ghostscript.
Ghostscript is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY. No author or distributor accepts responsibility
to anyone for the consequences of using it or for whether it serves any
particular purpose or works at all, unless he says so in writing. Refer
to the Ghostscript General Public License for full details.
Everyone is granted permission to copy, modify and redistribute
Ghostscript, but only under the conditions described in the Ghostscript
General Public License. A copy of this license is supposed to have been
given to you along with Ghostscript so you can know your rights and
responsibilities. It should be in a file named COPYING. Among other
things, the copyright notice and this notice must be preserved on all
copies. */
/* trace.c */
/* Tracing package for Turbo C */
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdarg.h>
#include <dos.h>
#include <setjmp.h>
#include "memory_.h"
typedef unsigned char byte;
#ifndef __TURBOC__ /* ****** HACK ****** */
# define _ds
# define _ss
extern unsigned _stklen;
# define jb_bp(jbuf) (unsigned long)buf[0]
#else /* ****** HACK ****** */
# define jb_bp(jbuf) buf[0].j_bp
#endif
/*
* NOTE: this package is extremely specialized. It works only
* with the Borland C and C++ compilers, with large code models,
* on 80x86 and 80x88 processors and compatibles with x>0.
* Use at your own risk.
*/
/*
* To trace a procedure, call
* trace(proc, "procname", NULL, retsize);
* or
* trace_name("_PROCNAME", mapfile, NULL, retsize);
* where retsize is sizeof the return value type.
* To print arguments formatted (with vprintf), use
* trace(proc, "procname", "argformat", retsize);
* or
* trace_name("_PROCNAME", mapfile, "argformat", retsize);
*
* If trace_flush_flag is true, for crash-prone programs,
* the trace output is flushed after every call or return.
*
* Trace output goes to the file trace_output_file, or to stdout
* if trace_output_file is not initialized.
*/
/*
* To map through a symbol (.MAP) file, open the file with
* mapfile = trace_open_map(filename, &reloc);
* which returns NULL if the map file isn't available.
* reloc is set to the relocation offset (i.e., the difference between
* actual procedure addresses and the values in the map file);
* if &reloc = NULL, nothing is stored. Then call
* sym = trace_next_symbol(&segaddr, mapfile);
* until it returns NULL.
* To look up a symbol in the file, call
* segaddr = trace_find_symbol(name, mapfile);
*/
#ifdef TRACEDEBUG
/* Main program for testing */
main()
{ int fib(int);
char *reloc;
FILE *trace_open_map();
FILE *mapf = trace_open_map("trace.map", NULL);
char *pfib;
char *trace_find_symbol();
pfib = trace_find_symbol("_FIB", mapf);
printf("fib at %lx\n", pfib);
trace_name("_FIB", mapf, "(%d)", sizeof(int));
fib(5);
}
int
fib(n)
int n;
{ if ( n <= 2 ) return 1;
return fib(n-1) + fib(n-2);
}
/* End of main program */
#endif
/* ------ Symbol file mapping ------ */
#define max_sym 40
static char sym[max_sym+1];
FILE *
trace_open_map(mapname, preloc)
char *mapname;
long *preloc;
{ FILE *mapf = fopen(mapname, "rt");
char *trace_find_symbol(char *, FILE *);
if ( mapf == NULL ) return NULL; /* can't open */
if ( preloc != NULL )
{ extern main();
char *main_addr = trace_find_symbol("_MAIN", mapf);
if ( main_addr == NULL )
{ fclose(mapf); /* can't find _main */
return NULL;
}
*preloc = (long)main - (long)main_addr;
}
return mapf;
}
char *
trace_next_symbol(paddr, mapf)
char **paddr;
FILE *mapf;
{ /* A symbol definition line looks like this: */
/* ssss:oooo namestring */
/* with exactly 7 spaces between the address and the name. */
while ( 1 )
{ char ch;
unsigned parm[2];
static char term[2] = ": ";
int pi, i;
if ( (ch = getc(mapf)) != ' ' ) goto skip;
for ( pi = 0; pi < 2; pi++ )
{ unsigned p = 0;
for ( i = 0; i < 4; i++ )
{ ch = getc(mapf);
if ( !isxdigit(ch) ) goto skip;
p = (p << 4) + (isdigit(ch) ? ch - '0' :
(ch - 'A' + 10) & 0xf);
}
if ( (ch = getc(mapf)) != term[pi] ) goto skip;
parm[pi] = p;
}
for ( i = 1; i < 7; i++ )
if ( (ch = getc(mapf)) != ' ' ) goto skip;
i = 0;
while ( (ch = getc(mapf)) != '\n' )
{ if ( i == max_sym ) goto skip; /* name too long */
sym[i++] = ch;
}
sym[i] = 0;
/* Success. Adjust the segment and return. */
*paddr = MK_FP(parm[0] + FP_SEG(abort), parm[1]);
return sym;
skip: /* Syntax didn't match, skip the rest of the line. */
while ( ch != '\n' )
{ if ( ch == (char)EOF && feof(mapf) ) return NULL;
ch = getc(mapf);
}
}
}
/* Look up a symbol in a file */
char *
trace_find_symbol(name, mapf)
char *name;
FILE *mapf;
{ char *s;
char *r;
rewind(mapf);
while ( (s = trace_next_symbol(&r, mapf)) != NULL )
if ( !strcmp(s, name) ) return r;
return (char *)NULL;
}
/* ------ Instruction and register set ------ */
/* Opcodes */
#define op_ADD_IMM 0x81
#define op_ADD_IMM8 0x83
#define op_ADD_IMMX_X 0
#define op_CALL_FAR 0x9a
#define op_CMP_MR 0x39
#define op_CMP_RM 0x3b
#define op_ENTER 0xc8
#define op_INC_REG 0x40
#define op_JMP_FAR 0xea
#define op_JMP_FAR_IND 0xff
#define op_JMP_FAR_IND_X 0x28
#define op_MOV_MR 0x8b
#define op_MOV_RM 0x89
#define op_NOP 0x90
#define op_POP_REG 0x58
#define op_PUSH_IMM 0x68
#define op_PUSH_REG 0x50
#define op_RETF 0xcb
#define op_SUB_IMM 0x81
#define op_SUB_IMM8 0x83
#define op_SUB_IMMX_X 0x28
/* Register numbers */
#define r_AX 0
#define r_CX 1
#define r_DX 2
#define r_BX 3
#define r_SP 4
#define r_BP 5
#define r_SI 6
#define r_DI 7
/*
* The standard entry sequence generated by Turbo C consists of:
* If there are no arguments and no local variables, nothing;
* if there are args but no local variables, PUSH BP, MOV BP,SP;
* if there are local variables, ENTER size,0, or
* PUSH BP, MOV BP,SP, SUB SP,<size>
* Optionally, PUSH SI;
* if the PUSH SI is present, optionally PUSH DI.
* If the frame is larger than a certain size (0x80?),
* CMP BP,SP; JB .+8.
* CMP [_STKLEN],SP; JA <ok>.
*/
/* ------ Stack parsing ------ */
typedef byte *i_ptr;
/* Record for BP and return */
typedef struct bp_ret_s {
unsigned _ss *bp;
i_ptr ret;
} bp_ret;
/* Forward declarations */
void fprint_block(int *, int, FILE *);
/* ------ Dynamic tracing ------ */
/* Flush output flag */
int trace_flush_flag = 0;
/* The file to use for tracing output */
FILE *trace_output_file = NULL;
/* The code sequence that replaces the standard procedure entry: */
typedef struct trace_entry_code_s {
byte JMP_FAR;
i_ptr entry;
} trace_entry_code;
static trace_entry_code trace_entry_template = {
op_JMP_FAR
};
/* The entry code in the trace record */
typedef struct entry_code_s {
byte PUSH_BP;
byte MOV_BP_SP, mov_X;
byte PUSH_BP2;
byte PUSH_seg; unsigned short rec_seg;
byte PUSH_off; unsigned short rec_off; /*struct trace_rec_s near **/
byte CALL; i_ptr print_args;
/* print_args moves the old return and BP above the args, */
/* and returns a new fake BP as the value in AX. */
byte MOV_BP_AX, mov_X2;
byte INC_SP, INC_SP2; /* pop 1 excess word */
/* The largest possible finishing code is: */
/* PUSH BP, MOV SP,BP, SUB SP,size, PUSH SI, PUSH DI, */
/* CMP [_STKLEN],SP, JMP FAR real_entry */
byte finish[18];
} entry_code;
static entry_code entry_template = {
op_PUSH_REG+r_BP,
op_MOV_MR, 0xc0+(r_BP<<3)+r_SP,
op_PUSH_REG+r_BP,
op_PUSH_IMM, 0,
op_PUSH_IMM, 0,
op_CALL_FAR, 0L,
op_MOV_MR, 0xc0+(r_BP<<3)+r_AX,
op_INC_REG+r_SP, op_INC_REG+r_SP
};
/* The exit code in the trace record */
typedef struct exit_code_s {
byte PUSH_BP;
byte MOV_BP_SP, mov_X;
byte PUSH_BP2;
byte PUSH_DX;
byte PUSH_AX;
byte PUSH_seg; unsigned short rec_seg;
byte PUSH_off; unsigned short rec_off; /*struct trace_rec_s near **/
byte CALL; i_ptr print_result;
/* print_result shuffles things back again */
byte ADD_SP, add_X, c6w;
byte MOV_BP_SP2, mov_X2;
byte POP_BP;
byte RETF;
} exit_code;
static exit_code exit_template = {
op_PUSH_REG+r_BP,
op_MOV_MR, 0xc0+(r_BP<<3)+r_SP,
op_PUSH_REG+r_BP,
op_PUSH_REG+r_DX,
op_PUSH_REG+r_AX,
op_PUSH_IMM, 0,
op_PUSH_IMM, 0,
op_CALL_FAR, 0L,
op_ADD_IMM8, op_ADD_IMMX_X+0xc0+r_SP, 12,
op_MOV_MR, 0xc0+(r_BP<<3)+r_SP,
op_POP_REG+r_BP,
op_RETF
};
/* Trace information record */
typedef struct trace_rec_s trace_rec;
struct trace_rec_s {
entry_code entry_code;
exit_code exit_code;
trace_rec *next;
char *name;
char *arg_format;
int retsize;
long count;
};
static trace_rec *trace_list = 0;
/* Trace a named procedure */
int
trace_name(name, mapf, arg_format, retsize)
char *name;
FILE *mapf;
char *arg_format;
int retsize;
{ char *proc = trace_find_symbol(name, mapf);
if ( proc == (char *)NULL ) return -1; /* name not found */
return trace((void (*)())proc, name, arg_format, retsize);
}
/* Trace a procedure */
int
trace(proc, name, arg_format, retsize)
void (*proc)();
char *name;
char *arg_format;
int retsize;
{ i_ptr pcode = (i_ptr)proc;
i_ptr pc = pcode;
i_ptr pt;
int len;
trace_entry_code *ptrace = (trace_entry_code *)pcode;
bp_ret *trace_print_arguments();
long trace_print_result();
trace_rec *rec = (trace_rec *)malloc(sizeof(trace_rec));
if ( !rec ) return -1;
rec->entry_code = entry_template;
rec->exit_code = exit_template;
/* Compare the procedure's entry sequence against the */
/* standard one. If they differ, we can't trace the procedure. */
pt = (byte *)&rec->entry_code.finish;
if ( *pc == op_ENTER )
pc += 4;
else if ( *pc == op_PUSH_REG+r_BP && pc[1] == op_MOV_MR &&
pc[2] == 0xc0+(r_BP<<3)+r_SP )
{ pc += 3;
if ( *pc == op_SUB_IMM && pc[1] == op_SUB_IMMX_X+0xc0+r_SP )
pc += 4;
else if ( *pc == op_SUB_IMM8 && pc[1] == op_SUB_IMMX_X+0xc0+r_SP )
pc += 3;
}
if ( *pc == op_PUSH_REG+r_SI )
{ pc++;
if ( *pc == op_PUSH_REG+r_DI ) pc++;
}
if ( *pc == op_CMP_MR && pc[1] == (r_SP<<3)+6 && *(char _ds **)(pc + 2) == (char _ds *)&_stklen )
pc += 4;
len = pc - pcode;
if ( len < sizeof(trace_entry_code) )
{ /* Not enough room for entry code */
free((char *)rec);
return -1;
}
/* There is, unfortunately, no far version of memcpy. */
movedata(FP_SEG(pcode), FP_OFF(pcode), FP_SEG(pt), FP_OFF(pt), len);
pt += len;
*pt++ = op_JMP_FAR;
*(byte **)pt = pc;
rec->next = trace_list;
rec->name = name;
rec->arg_format = arg_format;
rec->retsize = retsize;
rec->entry_code.rec_seg = FP_SEG(rec);
rec->entry_code.rec_off = FP_OFF(rec);
rec->entry_code.print_args = (i_ptr)trace_print_arguments;
rec->exit_code.rec_seg = FP_SEG(rec);
rec->exit_code.rec_off = FP_OFF(rec);
rec->exit_code.print_result = (i_ptr)trace_print_result;
rec->count = 0;
trace_list = rec;
/* Patch the procedure entry */
*ptrace = trace_entry_template;
ptrace->entry = (i_ptr)&rec->entry_code;
return 0;
}
/* Acquire the file to be used for tracing output. */
static FILE *
outfile()
{ return (trace_output_file == NULL ? stdout : trace_output_file);
}
/* Flush the output file if appropriate. */
static void
flushout()
{ if ( trace_output_file == NULL || trace_output_file == stdout )
fflush(stdout);
}
/* The following routine gets called at the entry */
/* to the traced procedure. It prints the arguments and returns. */
bp_ret *
trace_print_arguments(rec, sp)
trace_rec *rec;
bp_ret _ss *sp;
{ bp_ret saved_bp_ret = *sp;
byte *retcode = saved_bp_ret.ret;
byte *morecode = (byte *)&rec->exit_code.PUSH_BP;
char _ss *args = (char _ss *)(sp + 1);
int argsize = 0;
bp_ret _ss *above;
FILE *f = outfile();
rec->count++;
/* Get the size of the arguments by looking at */
/* the instructions after the return */
if ( retcode[0] == op_ADD_IMM8 && retcode[1] == op_ADD_IMMX_X+0xc0+r_SP )
argsize = retcode[2];
else if ( retcode[0] == op_ADD_IMM && retcode[1] == op_ADD_IMMX_X+0xc0+r_SP )
argsize = *(int *)(retcode + 2);
else if ( retcode[0] == op_INC_REG+r_SP && retcode[1] == op_INC_REG+r_SP )
argsize = 2;
else if ( retcode[0] == op_POP_REG+r_CX )
argsize = (retcode[1] == op_POP_REG+r_CX ? 4 : 2);
else if ( retcode[0] == op_MOV_MR && retcode[1] == 0xc0+(r_SP<<3)+r_BP )
argsize = (char _ss *)saved_bp_ret.bp - args;
else
printf("Unknown calling sequence at %x:%x, abort\n",
FP_SEG(retcode), FP_OFF(retcode)),
exit(1);
fprintf(f, "%s called from %x:%x with ",
rec->name, FP_SEG(retcode), FP_OFF(retcode));
if ( rec->arg_format == NULL )
fprint_block((int *)args, argsize >> 1, f);
else
vfprintf(f, rec->arg_format, (va_list)args);
fputc('\n', f);
flushout();
/* Push the arguments down, saving the return and BP above them. */
/* Note that this smashes the arguments of trace_print_arguments. */
memcpy((char *)(args - sizeof(bp_ret)), (char *)args, argsize);
args -= sizeof(bp_ret);
above = (bp_ret _ss *)(args + argsize);
*above = saved_bp_ret;
((byte **)args)[-1] = morecode;
return above;
}
/* Print and return the result */
long
trace_print_result(rec, result, sp, bp)
trace_rec *rec;
long result;
int _ss *sp;
bp_ret _ss *bp;
{ bp_ret temp_bp_ret;
FILE *f = outfile();
fprintf(f, "%s returns", rec->name);
switch ( rec->retsize )
{
case 0: break;
case 1: fprintf(f, " %x", (unsigned char)result); break;
case 2: fprintf(f, " %x", (unsigned short)result); break;
case 3: /* ??? */
case 4: fprintf(f, " %lx", result); break;
default:
{ /* We are returning a structure */
fputs(" (struct)", f);
fprint_block((int *)result, rec->retsize >> 1, f);
}
}
fputc('\n', f);
flushout();
/* Move the saved BP and return back down below the args. */
/* We can smash the args at this point without concern. */
/* However, since the source and destination may overlap, */
/* and Turbo C isn't smart enough to handle this case, */
/* we have to use an intermediate variable. */
temp_bp_ret = *bp;
*(bp_ret *)(sp + 1) = temp_bp_ret;
return result;
}
/* Procedure to print a block of words */
static void
fprint_block(ptr, count, f)
int *ptr;
int count;
FILE *f;
{ int i;
for ( i = 0; i < count; i++ )
fprintf(f, "%5x", ptr[i]);
}